//客户端程序

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<unistd.h>
#include<netdb.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
using namespace std;

class ctcpclient    //TCP通讯的客户端类
{
public:
    int m_clientfd;                 //客户端的socket，-1表示未连接成功或链接已断开，>=0表示有效的socket
    string m_ip;                    //服务端的IP/域名
    unsigned short m_port;          //通讯端口

    ctcpclient():m_clientfd(-1){}   //构造函数

    //向服务器发起链接请求，成功返回true，失败返回false
    bool connect(const string &in_ip, const unsigned short in_port)
    {
        if(m_clientfd != -1)        //如果socket已连接，直接返回失败
            return false;
        
        m_ip = in_ip;
        m_port = in_port;           //把服务端的IP和端口保存到成员变量中


        //第 1 步:创建客户端的socket。
        m_clientfd = socket(AF_INET, SOCK_STREAM, 0);
        if(m_clientfd == -1)
        {
            return false;
        }

        //第 2 步:向服务器发起链接请求。
        struct sockaddr_in servaddr;                //用于存放协议、端口和IP地址的结构体
        memset(&servaddr, 0, sizeof(servaddr));     //初始化
        servaddr.sin_family = AF_INET;              //1)协议族，IPV4为AF_INET
        servaddr.sin_port = htons(m_port);          //2)指定服务端的通信端口
                //htons()将 主机字节序(32位无符号整型) 转换成 网络字节序(32位无符号整型)
                //主机字节序(CPU字节序)有的是大端序有的是小端序，网络字节序只能是大端序

        struct hostent* h;              //用于存放服务器IP地址(大端序)的结构体指针
        if((h = gethostbyname(m_ip.c_str())) == nullptr)     //把域名/主机名/字符串格式的IP转换成结构体
        {
            ::close(m_clientfd);
            m_clientfd = -1;        //关于下文的:memcpy函数，memcpy(void *destin, void *source, unsigned n);
            return false;           //soource指向地址为起点，将连续n个字节的数据复制到destin指向地址为起点的内存中
        }                           //#include<cstring>
        memcpy（&servaddr.sin_addr, h->h_addr, h->h_length);      //3)指定服务端的IP(大端序)
        //servaddr.sin_addr.s_addr=inet_addr(argv[1]);          //3)指定服务端的IP，只能用IP熊用域名和主机名，不推荐

        if(::connect(m_clientfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == -1)   //向服务端发起连接请求
        {   //connect()库函数与成员函数重名，调用库函数加::即可
            ::close(m_clientfd);
            m_clientfd = -1;
            return false;
        }
        return true;
    }

    //向服务端发送请求报文,成功返回true, 失败返回false
    bool send(const string &buffer)     //buffer 不要用const char*,它只支持C风格字符串，string不行
    {                                   //const string &buffer 就可以两者兼顾
        if(m_clientfd == -1)
            return false;       //如果socket未连接，直接返回失败

        //if((::send(m_clientfd, buffer.c_str(), buffer.length(), 0)) <= 0) //其他写法,功能没问题，语义不准确，buffer可能是字符串，也可能是一块内存
        if((::send(m_clientfd, buffer.data(), buffer.size(), 0)) <= 0)
        {
            return false;
        }
        return true;
    }
     
    //接收服务端的报文，成功返回true， 失败返回false。
    //buffer-存放接收到的报文的内容，maxlen-本次接收报文的最大长度。
    bool recv(string &buffer, const size_t maxlen)
    {//如果直接操作string对象的内存，必须保证:1)不能越界;2)操作后手动设置数据的大小。

        buffer.clear();             //清空容器。
        buffer.resize(maxlen);      //设置容器的大小为maxlen
        int readn = ::recv(m_clientfd, &buffer[0], buffer.size(), 0);   //直接操作buffer的内存
        if( readn<=0)           //socket=-1,失败; =0 socket连接已断开; >0 成功接收到了数据
        {
            buffer.clear();
            return false; 
        }  
        buffer.resize(readn);       //重置buffer的实际大小

        return true;
    }

    //断开与服务端的连接
    bool close()
    {
        if(m_clientfd == -1)
        {
            return false;       //如果socket的状态是未连接，直接返回失败
        }
        ::close(m_clientfd);
        m_clientfd = -1;
        return true;
    }

    ~ctcpclient(){close();}      //析构函数
};





int main(int argc, char* argv[])
{
    if(argc!=3)
    {
        cout << "Using:./demo7 服务端的IP 服务端的端口\nExample:./demo07 192.168.101.138 5005 \n\n";
        return -1;
    }

    ctcpclient tcpclient;
    if(tcpclient.connect(argv[1], atoi(argv[2])) == false)   //向服务端发起连接请求
    {
        perror("connect()");
        return -1;
    }

    //第3步: 与服务端通讯，客户发送一个而请求报文后等待服务端的回复，收到回复后，再发送下一个报文
    string buffer;
    for(int ii=0; ii<10; ii++)          //循环3次，将与服务端进行三次通讯
    {
        
        buffer =  "这是第"+to_string(ii+1)+"个超级女生，编号"+to_string(ii+1)+"。";     //生成请求报文内容
        //向服务端发送请求报文
        if((tcpclient.send(buffer) == false)
        {
            perror("send");
            break;
        }
        cout << "发送:" << buffer << endl;

        
        //接收服务端的回应报文，如果服务端没有发送回应报文，recv()函数将阻塞等待
        if(tcpclient.recv(buffer, 1024) == false)
        {
            perror("recv()");
            break;
        }
        cout << "接收: " << buffer << endl;
        sleep(1);
    }

    //第4步: 关闭socket, 释放资源
    //析构函数调用close();

}












